package controller

import (
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"

	"commonUtils/utils"
	"commonUtils/web"
	"commonUtils/web/session"
)

const (
	SESSION_USER_KEY = "SESSION_USER_KEY"
	COOKIE_TOKEN_KEY = "TOKEN"
)

type Context struct {
	session.Session
	C              Controller
	W              http.ResponseWriter
	R              *http.Request
	UrlData        url.Values
	ClientData     interface{}
	bodyData       map[string]interface{}
	Code           int
	actionName     string
	controllerName string
	spaceName      string
}

func NewContext(c Controller, actionName string, w http.ResponseWriter, r *http.Request) *Context {
	ctx := &Context{
		C:              c,
		W:              w,
		R:              r,
		controllerName: c.GetName(),
		actionName:     strings.ToLower(actionName),
	}
	ctx.Session = web.SessionStart(w, r)
	ctx.UrlData = r.URL.Query()

	return ctx
}

func (c *Context) PostData() map[string]interface{} {
	body, err := c.BodyData()
	if err != nil {
		utils.Error("get post data error : %s", err)
		return nil
	}
	return body
}

func (c *Context) GetPostData(name string) interface{} {
	pdata := c.PostData()
	if len(pdata) > 0 {
		if data, ok := pdata[name]; ok {
			return data
		}
	}
	return ""
}

func (c *Context) ParseAllPostData(out interface{}) error {
	data := c.PostData()
	err := utils.MapToStruct(data, out)
	if err != nil {
		return err
	}
	return nil
}

func (c *Context) ParsePostData(name string, out interface{}) error {
	data := c.GetPostData(name)
	bData, err := utils.ToByte(data)
	if err != nil {
		return err
	}
	err = utils.ToStruct(bData, out)
	if err != nil {
		err = utils.JsonToStruct(string(bData), out)
		if err != nil {
			utils.Debug("data : %v", data)
			return err
		}
	}

	return nil
}

func (c *Context) SetBodyData(data map[string]interface{}) {
	c.bodyData = data
}

func (c *Context) BodyData() (map[string]interface{}, error) {
	if nil == c.bodyData {
		data, err := ioutil.ReadAll(c.R.Body)
		if err != nil {
			return nil, err
		}
		dataStr := string(data)
		c.bodyData, err = utils.JsonToMap(dataStr)
		if err != nil {
			dataValues, err := url.ParseQuery(dataStr)
			if err != nil {
				return nil, err
			}
			c.bodyData = make(map[string]interface{})
			for k, v := range dataValues {
				c.bodyData[k] = v
			}
		}
	}
	return c.bodyData, nil
}

func (c *Context) GetBodyData(name string) (interface{}, error) {
	pdata, err := c.BodyData()
	if err != nil {
		return nil, err
	}
	if len(pdata) > 0 {
		if data, ok := pdata[name]; ok {
			return data, nil
		} else {
			return nil, fmt.Errorf("name %s not exists", name)
		}
	}
	return "", nil
}

func (c *Context) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
	err := c.R.ParseMultipartForm(5 * 1024 * 1024 * 1024)
	if err != nil {
		return nil, nil, fmt.Errorf("Error ParseMultipartForm: %s", err)
	}
	return c.R.FormFile(key)
}

func (c *Context) SetUserID(id string) {
	c.Set(SESSION_USER_KEY, id)
}

func (c *Context) GetToken() (string, error) {
	cookie, err := c.R.Cookie(COOKIE_TOKEN_KEY)
	if err != nil {
		token := c.GetHeader("Authorization")
		if token != "" {
			arr := strings.Split(token, " ")
			if len(arr) >= 2 {
				switch arr[0] {
				case "GoToken":
					return arr[1], nil
				}
			}
		}
		return "", err
	}
	return cookie.Value, nil
}

func (c *Context) GetUserID() string {
	id := c.Get(SESSION_USER_KEY)
	if nil != id {
		return id.(string)
	}
	return ""
}

func (c *Context) IP() string {
	ip := utils.Match(`\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`, c.R.RemoteAddr)
	if "" == ip {
		ip = "localhost"
	}
	return ip
}

func (c *Context) ErrorApiWithData(msg string, data interface{}) {
	c.api(50000, msg, data)
}

func (c *Context) ErrorApi(msg string) {
	c.api(50000, msg, nil)
}

func (c *Context) ErrorApiWithCode(msg string, code int) {
	c.api(code, msg, nil)
}

func (c *Context) Success(msg string, data interface{}) {
	c.api(20000, msg, data)
}

func (c *Context) api(code int, msg string, data interface{}) {
	c.Json(map[string]interface{}{
		"code":    code,
		"message": msg,
		"data":    data,
	})
}

func (c *Context) GetData(name string) string {
	if c.UrlData.Has(name) {
		return c.UrlData.Get(name)
	}
	return ""
}

func (c *Context) GetArray(name string) []string {
	if c.UrlData.Has(name) {
		return c.UrlData[name]
	}
	return nil
}

func (c *Context) GetHeader(name string) string {
	return c.R.Header.Get(name)
}

func (c *Context) SetHeader(name string, data string) {
	c.W.Header().Add(name, data)
}

func (c *Context) ShowTemplate(name string, data interface{}) {
	temps := c.C.Template()
	if utils.IsNil(temps) {
		c.NotFound("template is not found!")
		return
	}
	temps.ExecuteTemplate(c.W, name, data)
}

func (c *Context) ServerError(msg string) {
	if "" == msg {
		msg = "error occur in the process!"
	}
	c.Error(msg, http.StatusInternalServerError)
}

func (c *Context) NotFound(msg string) {
	if "" == msg {
		msg = "content is not found!"
	}
	c.Error(msg, http.StatusNotFound)
}

func (c *Context) Json(d interface{}) {
	str, err := utils.ToJson(d)
	if err != nil {
		c.Error(err.Error(), http.StatusInternalServerError)
	} else {
		c.String(str)
	}
}

func (c *Context) GetParams() url.Values {
	return c.UrlData
}

func (c *Context) GetParam(name string) ([]string, bool) {
	if data, ok := c.UrlData[name]; ok {
		return data, ok
	}
	return nil, false
}

func (c *Context) ErrTxt(msg string) {
	c.Error(msg, 500)
}

func (c *Context) Error(msg string, code int) {
	c.Code = code
	http.Error(c.W, msg, code)
}

func (c *Context) Text(s string) {
	c.String(s)
}

func (c *Context) String(s string) {
	c.write([]byte(s))
}

func (c *Context) write(d []byte) {
	c.Code = http.StatusOK
	c.W.Write(d)
}

func (c *Context) DispatchIndexMethod(get, post, put, del func(*Context)) bool {
	if c.IsGET() {
		get(c)
	} else if c.IsPOST() {
		post(c)
	} else if c.IsPUT() {
		put(c)
	} else if c.IsDELETE() {
		del(c)
	} else {
		c.ErrorApi("错误的Method")
		return false
	}
	return true
}

func (c *Context) IsGET() bool {
	return c.Method() == "GET"
}
func (c *Context) IsPOST() bool {
	return c.Method() == "POST"
}
func (c *Context) IsPUT() bool {
	return c.Method() == "PUT"
}
func (c *Context) IsDELETE() bool {
	return c.Method() == "DELETE"
}

func (c *Context) Method() string {
	return c.R.Method
}

func (c *Context) GetActionName() string {
	return c.actionName
}

func (c *Context) GetControllerName() string {
	return c.controllerName
}
